{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "700be018-d401-4945-beb7-bcb00b39549c",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Looking in indexes: https://repo.huaweicloud.com/repository/pypi/simple/\n",
      "Requirement already satisfied: download in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (0.3.5)\n",
      "Requirement already satisfied: tqdm in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (4.67.1)\n",
      "Requirement already satisfied: six in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (1.16.0)\n",
      "Requirement already satisfied: requests in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from download) (2.32.3)\n",
      "Requirement already satisfied: charset-normalizer<4,>=2 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (3.4.1)\n",
      "Requirement already satisfied: idna<4,>=2.5 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (3.10)\n",
      "Requirement already satisfied: urllib3<3,>=1.21.1 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (2.3.0)\n",
      "Requirement already satisfied: certifi>=2017.4.17 in /home/mindspore/miniconda/envs/jupyter/lib/python3.9/site-packages (from requests->download) (2025.1.31)\n",
      "\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m A new release of pip is available: \u001b[0m\u001b[31;49m25.0.1\u001b[0m\u001b[39;49m -> \u001b[0m\u001b[32;49m25.2\u001b[0m\n",
      "\u001b[1m[\u001b[0m\u001b[34;49mnotice\u001b[0m\u001b[1;39;49m]\u001b[0m\u001b[39;49m To update, run: \u001b[0m\u001b[32;49mpython -m pip install --upgrade pip\u001b[0m\n"
     ]
    }
   ],
   "source": [
    "!pip install download"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "308655ae-2f06-4474-a987-bdcb131c57a4",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Creating data folder...\n",
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz (162.2 MB)\n",
      "\n",
      "file_sizes: 100%|████████████████████████████| 170M/170M [00:02<00:00, 75.3MB/s]\n",
      "Extracting tar.gz file...\n",
      "Successfully downloaded / unzipped to ./datasets-cifar10-bin\n"
     ]
    }
   ],
   "source": [
    "from download import download\n",
    "\n",
    "url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/datasets/cifar-10-binary.tar.gz\"\n",
    "\n",
    "download(url, \"./datasets-cifar10-bin\", kind=\"tar.gz\", replace=True)\n",
    "import mindspore as ms\n",
    "import mindspore.dataset as ds\n",
    "import mindspore.dataset.vision as vision\n",
    "import mindspore.dataset.transforms as transforms\n",
    "from mindspore import dtype as mstype\n",
    "\n",
    "data_dir = \"./datasets-cifar10-bin/cifar-10-batches-bin\"  # 数据集根目录\n",
    "batch_size = 256  # 批量大小\n",
    "image_size = 32  # 训练图像空间大小\n",
    "workers = 4  # 并行线程个数\n",
    "num_classes = 10  # 分类数量\n",
    "\n",
    "\n",
    "def create_dataset_cifar10(dataset_dir, usage, resize, batch_size, workers):\n",
    "\n",
    "    data_set = ds.Cifar10Dataset(dataset_dir=dataset_dir,\n",
    "                                 usage=usage,\n",
    "                                 num_parallel_workers=workers,\n",
    "                                 shuffle=True)\n",
    "\n",
    "    trans = []\n",
    "    if usage == \"train\":\n",
    "        trans += [\n",
    "            vision.RandomCrop((32, 32), (4, 4, 4, 4)),\n",
    "            vision.RandomHorizontalFlip(prob=0.5)\n",
    "        ]\n",
    "\n",
    "    trans += [\n",
    "        vision.Resize(resize),\n",
    "        vision.Rescale(1.0 / 255.0, 0.0),\n",
    "        vision.Normalize([0.4914, 0.4822, 0.4465], [0.2023, 0.1994, 0.2010]),\n",
    "        vision.HWC2CHW()\n",
    "    ]\n",
    "\n",
    "    target_trans = transforms.TypeCast(mstype.int32)\n",
    "\n",
    "    # 数据映射操作\n",
    "    data_set = data_set.map(operations=trans,\n",
    "                            input_columns='image',\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    data_set = data_set.map(operations=target_trans,\n",
    "                            input_columns='label',\n",
    "                            num_parallel_workers=workers)\n",
    "\n",
    "    # 批量操作\n",
    "    data_set = data_set.batch(batch_size)\n",
    "\n",
    "    return data_set\n",
    "\n",
    "\n",
    "# 获取处理后的训练与测试数据集\n",
    "\n",
    "dataset_train = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                       usage=\"train\",\n",
    "                                       resize=image_size,\n",
    "                                       batch_size=batch_size,\n",
    "                                       workers=workers)\n",
    "step_size_train = dataset_train.get_dataset_size()\n",
    "\n",
    "dataset_val = create_dataset_cifar10(dataset_dir=data_dir,\n",
    "                                     usage=\"test\",\n",
    "                                     resize=image_size,\n",
    "                                     batch_size=batch_size,\n",
    "                                     workers=workers)\n",
    "step_size_val = dataset_val.get_dataset_size()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "id": "c4511346-36a1-4e61-ade6-99d92d46f60e",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Image shape: (256, 3, 32, 32), Label shape: (256,)\n",
      "Labels: [9 9 9 4 0 1]\n"
     ]
    },
    {
     "data": {
      "image/png": "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d911F/X391M6naabb76Zcrkc1Wo1+uxnP0u9vb2USqXo9ttvp1qtxsZoNpv0la98hVatWkXRaJQGBgbo85//vIh7jVO9L61qbBbafEea4H+zY8cOes973kM9PT105513UrPZpC9/+cu0aNEiFvfVr36VvvjFL9Ktt95KH//4x2lycpLuueceuuaaa2jbtm2UyWSIiOhnP/sZ3XjjjbR582b68pe/TLZt03333UfXXXcdPfXUU3T55ZezcW+55RZas2YN/Y//8T/I9/25um1wDvDCCy/QM888Q7fddhstXbqUhoaG6O///u/p2muvpd27d1MikfiNx09PT9ONN95It912G/3+7/8+m7MHDhyg3/3d36U//MM/pK1bt9J9991Ht9xyCz3yyCN0/fXXv+6YDz30EJXLZfqjP/oj6urqoueff57uueceGh4epoceeojFuq5LN9xwA11xxRX0V3/1V/TYY4/RX//1X9OqVavoj/7oj07G3XHHHXT//ffT7bffTp/5zGfoyJEj9Hd/93e0bds2evrppykcDp/mEwRngjc7D03+5m/+hj796U9TKpWiL3zhC0REJ+fm+Pg4XXnllVQul+kzn/kMdXV10QMPPEC/8zu/Q//yL/9CH/jAB9hYd999N8XjcfqzP/szOnjwIN1zzz0UDofJtm2anZ2lO++8k375y1/S/fffT4ODg/SlL33p5LEf//jH6YEHHqCbb76Z/uRP/oSee+45uvvuu2nPnj30r//6r+w8p/u+aCy4+e4D3/d9/6abbvJjsZh/9OjRk327d+/2HcfxX3tMQ0NDvuM4/le/+lV27I4dO/xQKHSy3/M8f82aNf4NN9zge553Mq5cLvuDg4P+9ddff7Lvy1/+sk9E/u/93u+dzdsD5zDlcln0Pfvssz4R+f/8z/98su/xxx/3ich//PHHT/Zt2bLFJyL/H/7hH8QYK1as8InIf/jhh0/25XI5f/Hixf6ll176G8fVrunuu+/2Lcti78jWrVt9IvL/+3//7yz20ksv9Tdv3nyy/dRTT/lE5D/44IMs7pFHHlH7wdzT6jx8bc0yue+++3wi8o8cOXKyb+PGjf6WLVtE7Gc/+1mfiPynnnrqZF+hUPAHBwf9gYEB33Vd3/f/z9zctGmTX6/XT8b+3u/9nm9Zln/jjTeycd/+9rf7K1asONnevn27T0T+xz/+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",
      "text/plain": [
       "<Figure size 640x480 with 6 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "import numpy as np\n",
    "\n",
    "data_iter = next(dataset_train.create_dict_iterator())\n",
    "\n",
    "images = data_iter[\"image\"].asnumpy()\n",
    "labels = data_iter[\"label\"].asnumpy()\n",
    "print(f\"Image shape: {images.shape}, Label shape: {labels.shape}\")\n",
    "\n",
    "# 训练数据集中，前六张图片所对应的标签\n",
    "print(f\"Labels: {labels[:6]}\")\n",
    "\n",
    "classes = []\n",
    "\n",
    "with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "    for line in f:\n",
    "        line = line.rstrip()\n",
    "        if line:\n",
    "            classes.append(line)\n",
    "\n",
    "# 训练数据集的前六张图片\n",
    "plt.figure()\n",
    "for i in range(6):\n",
    "    plt.subplot(2, 3, i + 1)\n",
    "    image_trans = np.transpose(images[i], (1, 2, 0))\n",
    "    mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "    std = np.array([0.2023, 0.1994, 0.2010])\n",
    "    image_trans = std * image_trans + mean\n",
    "    image_trans = np.clip(image_trans, 0, 1)\n",
    "    plt.title(f\"{classes[labels[i]]}\")\n",
    "    plt.imshow(image_trans)\n",
    "    plt.axis(\"off\")\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "id": "92646169-04ca-4d78-a161-db2ad62860cf",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from typing import Type, Union, List, Optional\n",
    "import mindspore.nn as nn\n",
    "from mindspore.common.initializer import Normal\n",
    "\n",
    "# 初始化卷积层与BatchNorm的参数\n",
    "weight_init = Normal(mean=0, sigma=0.02)\n",
    "gamma_init = Normal(mean=1, sigma=0.02)\n",
    "\n",
    "class ResidualBlockBase(nn.Cell):\n",
    "    expansion: int = 1  # 最后一个卷积核数量与第一个卷积核数量相等\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, norm: Optional[nn.Cell] = None,\n",
    "                 down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlockBase, self).__init__()\n",
    "        if not norm:\n",
    "            self.norm = nn.BatchNorm2d(out_channel)\n",
    "        else:\n",
    "            self.norm = norm\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.conv2 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=3, weight_init=weight_init)\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "        \"\"\"ResidualBlockBase construct.\"\"\"\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "id": "1799e48a-653b-41b6-83d9-197ad2ec4b53",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "class ResidualBlock(nn.Cell):\n",
    "    expansion = 4  # 最后一个卷积核的数量是第一个卷积核数量的4倍\n",
    "\n",
    "    def __init__(self, in_channel: int, out_channel: int,\n",
    "                 stride: int = 1, down_sample: Optional[nn.Cell] = None) -> None:\n",
    "        super(ResidualBlock, self).__init__()\n",
    "\n",
    "        self.conv1 = nn.Conv2d(in_channel, out_channel,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm1 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv2 = nn.Conv2d(out_channel, out_channel,\n",
    "                               kernel_size=3, stride=stride,\n",
    "                               weight_init=weight_init)\n",
    "        self.norm2 = nn.BatchNorm2d(out_channel)\n",
    "        self.conv3 = nn.Conv2d(out_channel, out_channel * self.expansion,\n",
    "                               kernel_size=1, weight_init=weight_init)\n",
    "        self.norm3 = nn.BatchNorm2d(out_channel * self.expansion)\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        self.down_sample = down_sample\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        identity = x  # shortcuts分支\n",
    "\n",
    "        out = self.conv1(x)  # 主分支第一层：1*1卷积层\n",
    "        out = self.norm1(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv2(out)  # 主分支第二层：3*3卷积层\n",
    "        out = self.norm2(out)\n",
    "        out = self.relu(out)\n",
    "        out = self.conv3(out)  # 主分支第三层：1*1卷积层\n",
    "        out = self.norm3(out)\n",
    "\n",
    "        if self.down_sample is not None:\n",
    "            identity = self.down_sample(x)\n",
    "\n",
    "        out += identity  # 输出为主分支与shortcuts之和\n",
    "        out = self.relu(out)\n",
    "\n",
    "        return out"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "id": "4bdd09c7-20cf-4cf1-86e2-802ce26cfa70",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def make_layer(last_out_channel, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "               channel: int, block_nums: int, stride: int = 1):\n",
    "    down_sample = None  # shortcuts分支\n",
    "\n",
    "    if stride != 1 or last_out_channel != channel * block.expansion:\n",
    "\n",
    "        down_sample = nn.SequentialCell([\n",
    "            nn.Conv2d(last_out_channel, channel * block.expansion,\n",
    "                      kernel_size=1, stride=stride, weight_init=weight_init),\n",
    "            nn.BatchNorm2d(channel * block.expansion, gamma_init=gamma_init)\n",
    "        ])\n",
    "\n",
    "    layers = []\n",
    "    layers.append(block(last_out_channel, channel, stride=stride, down_sample=down_sample))\n",
    "\n",
    "    in_channel = channel * block.expansion\n",
    "    # 堆叠残差网络\n",
    "    for _ in range(1, block_nums):\n",
    "\n",
    "        layers.append(block(in_channel, channel))\n",
    "\n",
    "    return nn.SequentialCell(layers)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "a4286738-ee27-4e89-a0e4-88d61d743d22",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "from mindspore import load_checkpoint, load_param_into_net\n",
    "\n",
    "\n",
    "class ResNet(nn.Cell):\n",
    "    def __init__(self, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "                 layer_nums: List[int], num_classes: int, input_channel: int) -> None:\n",
    "        super(ResNet, self).__init__()\n",
    "\n",
    "        self.relu = nn.ReLU()\n",
    "        # 第一个卷积层，输入channel为3（彩色图像），输出channel为64\n",
    "        self.conv1 = nn.Conv2d(3, 64, kernel_size=7, stride=2, weight_init=weight_init)\n",
    "        self.norm = nn.BatchNorm2d(64)\n",
    "        # 最大池化层，缩小图片的尺寸\n",
    "        self.max_pool = nn.MaxPool2d(kernel_size=3, stride=2, pad_mode='same')\n",
    "        # 各个残差网络结构块定义\n",
    "        self.layer1 = make_layer(64, block, 64, layer_nums[0])\n",
    "        self.layer2 = make_layer(64 * block.expansion, block, 128, layer_nums[1], stride=2)\n",
    "        self.layer3 = make_layer(128 * block.expansion, block, 256, layer_nums[2], stride=2)\n",
    "        self.layer4 = make_layer(256 * block.expansion, block, 512, layer_nums[3], stride=2)\n",
    "        # 平均池化层\n",
    "        self.avg_pool = nn.AvgPool2d()\n",
    "        # flattern层\n",
    "        self.flatten = nn.Flatten()\n",
    "        # 全连接层\n",
    "        self.fc = nn.Dense(in_channels=input_channel, out_channels=num_classes)\n",
    "\n",
    "    def construct(self, x):\n",
    "\n",
    "        x = self.conv1(x)\n",
    "        x = self.norm(x)\n",
    "        x = self.relu(x)\n",
    "        x = self.max_pool(x)\n",
    "\n",
    "        x = self.layer1(x)\n",
    "        x = self.layer2(x)\n",
    "        x = self.layer3(x)\n",
    "        x = self.layer4(x)\n",
    "\n",
    "        x = self.avg_pool(x)\n",
    "        x = self.flatten(x)\n",
    "        x = self.fc(x)\n",
    "\n",
    "        return x"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "id": "fe700d73-82f9-4719-a89f-0032d24ade01",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "def _resnet(model_url: str, block: Type[Union[ResidualBlockBase, ResidualBlock]],\n",
    "            layers: List[int], num_classes: int, pretrained: bool, pretrained_ckpt: str,\n",
    "            input_channel: int):\n",
    "    model = ResNet(block, layers, num_classes, input_channel)\n",
    "\n",
    "    if pretrained:\n",
    "        # 加载预训练模型\n",
    "        download(url=model_url, path=pretrained_ckpt, replace=True)\n",
    "        param_dict = load_checkpoint(pretrained_ckpt)\n",
    "        load_param_into_net(model, param_dict)\n",
    "\n",
    "    return model\n",
    "\n",
    "\n",
    "def resnet50(num_classes: int = 1000, pretrained: bool = False):\n",
    "    \"\"\"ResNet50模型\"\"\"\n",
    "    resnet50_url = \"https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt\"\n",
    "    resnet50_ckpt = \"./LoadPretrainedModel/resnet50_224_new.ckpt\"\n",
    "    return _resnet(resnet50_url, ResidualBlock, [3, 4, 6, 3], num_classes,\n",
    "                   pretrained, resnet50_ckpt, 2048)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "id": "2afb2807-967c-4fca-8bfd-f4805407086a",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Downloading data from https://mindspore-website.obs.cn-north-4.myhuaweicloud.com/notebook/models/application/resnet50_224_new.ckpt (97.7 MB)\n",
      "\n",
      "file_sizes: 100%|█████████████████████████████| 102M/102M [00:00<00:00, 261MB/s]\n",
      "Successfully downloaded file to ./LoadPretrainedModel/resnet50_224_new.ckpt\n"
     ]
    }
   ],
   "source": [
    "# 定义ResNet50网络\n",
    "network = resnet50(pretrained=True)\n",
    "\n",
    "# 全连接层输入层的大小\n",
    "in_channel = network.fc.in_channels\n",
    "fc = nn.Dense(in_channels=in_channel, out_channels=10)\n",
    "# 重置全连接层\n",
    "network.fc = fc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 12,
   "id": "7474f6b9-1a98-4c05-a926-e7a1369dd83b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "# 设置学习率\n",
    "num_epochs = 5\n",
    "lr = nn.cosine_decay_lr(min_lr=0.00001, max_lr=0.001, total_step=step_size_train * num_epochs,\n",
    "                        step_per_epoch=step_size_train, decay_epoch=num_epochs)\n",
    "# 定义优化器和损失函数\n",
    "opt = nn.Momentum(params=network.trainable_params(), learning_rate=lr, momentum=0.9)\n",
    "loss_fn = nn.SoftmaxCrossEntropyWithLogits(sparse=True, reduction='mean')\n",
    "\n",
    "\n",
    "def forward_fn(inputs, targets):\n",
    "    logits = network(inputs)\n",
    "    loss = loss_fn(logits, targets)\n",
    "    return loss\n",
    "\n",
    "\n",
    "grad_fn = ms.value_and_grad(forward_fn, None, opt.parameters)\n",
    "\n",
    "\n",
    "def train_step(inputs, targets):\n",
    "    loss, grads = grad_fn(inputs, targets)\n",
    "    opt(grads)\n",
    "    return loss"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "91e5f932-5884-466f-911c-090a228f1347",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import os\n",
    "\n",
    "# 创建迭代器\n",
    "data_loader_train = dataset_train.create_tuple_iterator(num_epochs=num_epochs)\n",
    "data_loader_val = dataset_val.create_tuple_iterator(num_epochs=num_epochs)\n",
    "\n",
    "# 最佳模型存储路径\n",
    "best_acc = 0\n",
    "best_ckpt_dir = \"./BestCheckpoint\"\n",
    "best_ckpt_path = \"./BestCheckpoint/resnet50-best.ckpt\"\n",
    "\n",
    "if not os.path.exists(best_ckpt_dir):\n",
    "    os.mkdir(best_ckpt_dir)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 14,
   "id": "84aca98f-cafc-4418-93e9-5ea627a52f67",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import mindspore.ops as ops\n",
    "\n",
    "\n",
    "def train(data_loader, epoch):\n",
    "    \"\"\"模型训练\"\"\"\n",
    "    losses = []\n",
    "    network.set_train(True)\n",
    "\n",
    "    for i, (images, labels) in enumerate(data_loader):\n",
    "        loss = train_step(images, labels)\n",
    "        if i % 100 == 0 or i == step_size_train - 1:\n",
    "            print('Epoch: [%3d/%3d], Steps: [%3d/%3d], Train Loss: [%5.3f]' %\n",
    "                  (epoch + 1, num_epochs, i + 1, step_size_train, loss))\n",
    "        losses.append(loss)\n",
    "\n",
    "    return sum(losses) / len(losses)\n",
    "\n",
    "\n",
    "def evaluate(data_loader):\n",
    "    \"\"\"模型验证\"\"\"\n",
    "    network.set_train(False)\n",
    "\n",
    "    correct_num = 0.0  # 预测正确个数\n",
    "    total_num = 0.0  # 预测总数\n",
    "\n",
    "    for images, labels in data_loader:\n",
    "        logits = network(images)\n",
    "        pred = logits.argmax(axis=1)  # 预测结果\n",
    "        correct = ops.equal(pred, labels).reshape((-1, ))\n",
    "        correct_num += correct.sum().asnumpy()\n",
    "        total_num += correct.shape[0]\n",
    "\n",
    "    acc = correct_num / total_num  # 准确率\n",
    "\n",
    "    return acc"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "001a9c37-7f08-4fc0-bf87-72a620ade241",
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Start Training Loop ...\n",
      "Epoch: [  1/  5], Steps: [  1/196], Train Loss: [2.380]\n",
      "Epoch: [  1/  5], Steps: [101/196], Train Loss: [1.420]\n",
      "Epoch: [  1/  5], Steps: [196/196], Train Loss: [1.046]\n",
      "--------------------------------------------------\n",
      "Epoch: [  1/  5], Average Train Loss: [1.628], Accuracy: [0.592]\n",
      "--------------------------------------------------\n",
      "Epoch: [  2/  5], Steps: [  1/196], Train Loss: [1.101]\n",
      "Epoch: [  2/  5], Steps: [101/196], Train Loss: [1.014]\n",
      "Epoch: [  2/  5], Steps: [196/196], Train Loss: [1.007]\n",
      "--------------------------------------------------\n",
      "Epoch: [  2/  5], Average Train Loss: [1.008], Accuracy: [0.682]\n",
      "--------------------------------------------------\n",
      "Epoch: [  3/  5], Steps: [  1/196], Train Loss: [0.867]\n",
      "Epoch: [  3/  5], Steps: [101/196], Train Loss: [0.827]\n",
      "Epoch: [  3/  5], Steps: [196/196], Train Loss: [0.653]\n",
      "--------------------------------------------------\n",
      "Epoch: [  3/  5], Average Train Loss: [0.842], Accuracy: [0.724]\n",
      "--------------------------------------------------\n",
      "Epoch: [  4/  5], Steps: [  1/196], Train Loss: [0.696]\n",
      "Epoch: [  4/  5], Steps: [101/196], Train Loss: [0.855]\n",
      "Epoch: [  4/  5], Steps: [196/196], Train Loss: [0.834]\n",
      "--------------------------------------------------\n",
      "Epoch: [  4/  5], Average Train Loss: [0.768], Accuracy: [0.739]\n",
      "--------------------------------------------------\n",
      "Epoch: [  5/  5], Steps: [  1/196], Train Loss: [0.763]\n",
      "Epoch: [  5/  5], Steps: [101/196], Train Loss: [0.810]\n"
     ]
    }
   ],
   "source": [
    "# 开始循环训练\n",
    "print(\"Start Training Loop ...\")\n",
    "\n",
    "for epoch in range(num_epochs):\n",
    "    curr_loss = train(data_loader_train, epoch)\n",
    "    curr_acc = evaluate(data_loader_val)\n",
    "\n",
    "    print(\"-\" * 50)\n",
    "    print(\"Epoch: [%3d/%3d], Average Train Loss: [%5.3f], Accuracy: [%5.3f]\" % (\n",
    "        epoch+1, num_epochs, curr_loss, curr_acc\n",
    "    ))\n",
    "    print(\"-\" * 50)\n",
    "\n",
    "    # 保存当前预测准确率最高的模型\n",
    "    if curr_acc > best_acc:\n",
    "        best_acc = curr_acc\n",
    "        ms.save_checkpoint(network, best_ckpt_path)\n",
    "\n",
    "print(\"=\" * 80)\n",
    "print(f\"End of validation the best Accuracy is: {best_acc: 5.3f}, \"\n",
    "      f\"save the best ckpt file in {best_ckpt_path}\", flush=True)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "47e4cf68-335b-4b9b-8f45-b7df8c0eae6b",
   "metadata": {
    "tags": []
   },
   "outputs": [],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "\n",
    "def visualize_model(best_ckpt_path, dataset_val):\n",
    "    num_class = 10\n",
    "    net = resnet50(num_class)\n",
    "    # 加载模型参数\n",
    "    param_dict = ms.load_checkpoint(best_ckpt_path)\n",
    "    ms.load_param_into_net(net, param_dict)\n",
    "    # 加载验证集的数据进行验证\n",
    "    data = next(dataset_val.create_dict_iterator())\n",
    "    images = data[\"image\"]\n",
    "    labels = data[\"label\"]\n",
    "    # 预测图像类别\n",
    "    output = net(data['image'])\n",
    "    pred = np.argmax(output.asnumpy(), axis=1)\n",
    "\n",
    "    # 图像分类\n",
    "    classes = []\n",
    "\n",
    "    with open(data_dir + \"/batches.meta.txt\", \"r\") as f:\n",
    "        for line in f:\n",
    "            line = line.rstrip()\n",
    "            if line:\n",
    "                classes.append(line)\n",
    "\n",
    "    # 显示图像及图像的预测值\n",
    "    plt.figure()\n",
    "    for i in range(6):\n",
    "        plt.subplot(2, 3, i + 1)\n",
    "        # 若预测正确，显示为蓝色；若预测错误，显示为红色\n",
    "        color = 'blue' if pred[i] == labels.asnumpy()[i] else 'red'\n",
    "        plt.title('predict:{}'.format(classes[pred[i]]), color=color)\n",
    "        picture_show = np.transpose(images.asnumpy()[i], (1, 2, 0))\n",
    "        mean = np.array([0.4914, 0.4822, 0.4465])\n",
    "        std = np.array([0.2023, 0.1994, 0.2010])\n",
    "        picture_show = std * picture_show + mean\n",
    "        picture_show = np.clip(picture_show, 0, 1)\n",
    "        plt.imshow(picture_show)\n",
    "        plt.axis('off')\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "# 使用测试数据集进行验证\n",
    "visualize_model(best_ckpt_path=best_ckpt_path, dataset_val=dataset_val)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "164f8d00-1473-4ecb-8579-ad1298547c48",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "0bf9fc1c-80f7-4022-b580-d0d45b908bd5",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.9.21"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
